{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 快速开始\n",
    "## 声明模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "unsupported operand type(s) for |: 'type' and 'NoneType'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[3], line 17\u001b[0m\n\u001b[1;32m     13\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mBase\u001b[39;00m(DeclarativeBase):\n\u001b[1;32m     14\u001b[0m     \u001b[38;5;28;01mpass\u001b[39;00m \n\u001b[0;32m---> 17\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mUser\u001b[39;00m(Base):\n\u001b[1;32m     18\u001b[0m     __tablename__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124muser_account\u001b[39m\u001b[38;5;124m'\u001b[39m \n\u001b[1;32m     19\u001b[0m     \u001b[38;5;28mid\u001b[39m: Mapped[\u001b[38;5;28mint\u001b[39m\u001b[38;5;241m|\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m] \u001b[38;5;241m=\u001b[39m mapped_column(types\u001b[38;5;241m.\u001b[39mInteger, primary_key\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, nullable\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n",
      "Cell \u001b[0;32mIn[3], line 19\u001b[0m, in \u001b[0;36mUser\u001b[0;34m()\u001b[0m\n\u001b[1;32m     17\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mUser\u001b[39;00m(Base):\n\u001b[1;32m     18\u001b[0m     __tablename__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124muser_account\u001b[39m\u001b[38;5;124m'\u001b[39m \n\u001b[0;32m---> 19\u001b[0m     \u001b[38;5;28mid\u001b[39m: Mapped[\u001b[38;5;28;43mint\u001b[39;49m\u001b[38;5;241;43m|\u001b[39;49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m] \u001b[38;5;241m=\u001b[39m mapped_column(types\u001b[38;5;241m.\u001b[39mInteger, primary_key\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, nullable\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m     20\u001b[0m     addresses: Mapped[\u001b[38;5;28mlist\u001b[39m[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAddress\u001b[39m\u001b[38;5;124m\"\u001b[39m]] \u001b[38;5;241m=\u001b[39m relationship(back_populates\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124muser\u001b[39m\u001b[38;5;124m'\u001b[39m, cascade\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mall, delete-orphan\u001b[39m\u001b[38;5;124m'\u001b[39m)    \n\u001b[1;32m     22\u001b[0m     name: Mapped[\u001b[38;5;28mstr\u001b[39m] \u001b[38;5;241m=\u001b[39m mapped_column(types\u001b[38;5;241m.\u001b[39mUnicode(\u001b[38;5;241m30\u001b[39m))\n",
      "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for |: 'type' and 'NoneType'"
     ]
    }
   ],
   "source": [
    "from sqlalchemy import (\n",
    "    ForeignKey,\n",
    "    types,\n",
    ")\n",
    "from sqlalchemy.orm import (\n",
    "    DeclarativeBase,\n",
    "    Mapped,\n",
    "    mapped_column,\n",
    "    relationship,\n",
    ")\n",
    "\n",
    "\n",
    "class Base(DeclarativeBase):\n",
    "    pass \n",
    "\n",
    "\n",
    "class User(Base):\n",
    "    __tablename__ = 'user_account' \n",
    "    id: Mapped[int|None] = mapped_column(types.Integer, primary_key=True, nullable=False)\n",
    "    addresses: Mapped[list[\"Address\"]] = relationship(back_populates='user', cascade='all, delete-orphan')    \n",
    "\n",
    "    name: Mapped[str] = mapped_column(types.Unicode(30))\n",
    "    fullname: Mapped[str|None]\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        return f\"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})\"\n",
    "\n",
    "\n",
    "class Address(Base):\n",
    "    __tablename__ = 'address' \n",
    "    id: Mapped[int|None] = mapped_column(types.Integer, primary_key=True, nullable=False)\n",
    "    user_id = mapped_column(ForeignKey('user_account.id'))\n",
    "    user: Mapped[\"User\"] = relationship(back_populates='addresses')\n",
    "\n",
    "    email_address: Mapped[str]\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        return f\"Address(id={self.id!r}, user_id={self.user_id!r}, email_address={self.email_address!r})\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 创建引擎"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sqlalchemy import create_engine\n",
    "\n",
    "\n",
    "engine = create_engine('sqlite://', echo=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 创建表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:23:24,887 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:23:24,889 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"user_account\")\n",
      "2024-07-28 20:23:24,890 INFO sqlalchemy.engine.Engine [raw sql] ()\n",
      "2024-07-28 20:23:24,892 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"user_account\")\n",
      "2024-07-28 20:23:24,893 INFO sqlalchemy.engine.Engine [raw sql] ()\n",
      "2024-07-28 20:23:24,894 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"address\")\n",
      "2024-07-28 20:23:24,895 INFO sqlalchemy.engine.Engine [raw sql] ()\n",
      "2024-07-28 20:23:24,896 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"address\")\n",
      "2024-07-28 20:23:24,897 INFO sqlalchemy.engine.Engine [raw sql] ()\n",
      "2024-07-28 20:23:24,899 INFO sqlalchemy.engine.Engine \n",
      "CREATE TABLE user_account (\n",
      "\tid INTEGER NOT NULL, \n",
      "\tname VARCHAR(30) NOT NULL, \n",
      "\tfullname VARCHAR, \n",
      "\tPRIMARY KEY (id)\n",
      ")\n",
      "\n",
      "\n",
      "2024-07-28 20:23:24,899 INFO sqlalchemy.engine.Engine [no key 0.00068s] ()\n",
      "2024-07-28 20:23:24,901 INFO sqlalchemy.engine.Engine \n",
      "CREATE TABLE address (\n",
      "\tid INTEGER NOT NULL, \n",
      "\tuser_id INTEGER, \n",
      "\temail_address VARCHAR NOT NULL, \n",
      "\tPRIMARY KEY (id), \n",
      "\tFOREIGN KEY(user_id) REFERENCES user_account (id)\n",
      ")\n",
      "\n",
      "\n",
      "2024-07-28 20:23:24,902 INFO sqlalchemy.engine.Engine [no key 0.00070s] ()\n",
      "2024-07-28 20:23:24,903 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "Base.metadata.create_all(engine)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 对象持久化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:23:24,919 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:23:24,921 INFO sqlalchemy.engine.Engine INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id\n",
      "2024-07-28 20:23:24,922 INFO sqlalchemy.engine.Engine [generated in 0.00008s (insertmanyvalues) 1/3 (ordered; batch not supported)] ('spongebob', 'Spongebob Squarepants')\n",
      "2024-07-28 20:23:24,922 INFO sqlalchemy.engine.Engine INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id\n",
      "2024-07-28 20:23:24,923 INFO sqlalchemy.engine.Engine [insertmanyvalues 2/3 (ordered; batch not supported)] ('sandy', 'Sandy Cheeks')\n",
      "2024-07-28 20:23:24,924 INFO sqlalchemy.engine.Engine INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id\n",
      "2024-07-28 20:23:24,924 INFO sqlalchemy.engine.Engine [insertmanyvalues 3/3 (ordered; batch not supported)] ('patrick', 'Patrick Star')\n",
      "2024-07-28 20:23:24,926 INFO sqlalchemy.engine.Engine INSERT INTO address (user_id, email_address) VALUES (?, ?) RETURNING id\n",
      "2024-07-28 20:23:24,926 INFO sqlalchemy.engine.Engine [generated in 0.00012s (insertmanyvalues) 1/3 (ordered; batch not supported)] (1, 'spongebob@sqlalchemy.org')\n",
      "2024-07-28 20:23:24,926 INFO sqlalchemy.engine.Engine INSERT INTO address (user_id, email_address) VALUES (?, ?) RETURNING id\n",
      "2024-07-28 20:23:24,926 INFO sqlalchemy.engine.Engine [insertmanyvalues 2/3 (ordered; batch not supported)] (2, 'sandy@sqlalchemy.org')\n",
      "2024-07-28 20:23:24,927 INFO sqlalchemy.engine.Engine INSERT INTO address (user_id, email_address) VALUES (?, ?) RETURNING id\n",
      "2024-07-28 20:23:24,927 INFO sqlalchemy.engine.Engine [insertmanyvalues 3/3 (ordered; batch not supported)] (2, 'sandy@squirrelpower.org')\n",
      "2024-07-28 20:23:24,928 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "from sqlalchemy.orm import Session\n",
    "\n",
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        spongebob = User(\n",
    "            name=\"spongebob\",\n",
    "            fullname=\"Spongebob Squarepants\",\n",
    "            addresses=[Address(email_address=\"spongebob@sqlalchemy.org\")],\n",
    "        )\n",
    "        sandy = User(\n",
    "            name=\"sandy\",\n",
    "            fullname=\"Sandy Cheeks\",\n",
    "            addresses=[\n",
    "                Address(email_address=\"sandy@sqlalchemy.org\"),\n",
    "                Address(email_address=\"sandy@squirrelpower.org\"),\n",
    "            ],\n",
    "     )\n",
    "        patrick = User(name=\"patrick\", fullname=\"Patrick Star\")\n",
    "        session.add_all([\n",
    "            spongebob, \n",
    "            sandy, \n",
    "            patrick])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 简单查询"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:27:58,142 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:27:58,144 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname \n",
      "FROM user_account \n",
      "WHERE user_account.name IN (?, ?)\n",
      "2024-07-28 20:27:58,144 INFO sqlalchemy.engine.Engine [cached since 273.2s ago] ('spongebob', 'sandy')\n",
      "User(id=1, name='spongebob', fullname='Spongebob Squarepants')\n",
      "User(id=2, name='sandy', fullname='Sandy Cheeks')\n",
      "2024-07-28 20:27:58,145 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "from sqlalchemy import select \n",
    "\n",
    "\n",
    "stmt = select(User).where(User.name.in_(['spongebob', 'sandy']))\n",
    "\n",
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        for user in session.execute(stmt).scalars().all():\n",
    "            print(user)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 连接查询"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:30:17,992 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:30:17,994 INFO sqlalchemy.engine.Engine SELECT address.id, address.user_id, address.email_address \n",
      "FROM address JOIN user_account ON user_account.id = address.user_id \n",
      "WHERE user_account.name = ? AND address.email_address = ?\n",
      "2024-07-28 20:30:17,996 INFO sqlalchemy.engine.Engine [generated in 0.00223s] ('sandy', 'sandy@sqlalchemy.org')\n",
      "Address(id=2, user_id=2, email_address='sandy@sqlalchemy.org')\n",
      "2024-07-28 20:30:17,997 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        stmt = (\n",
    "            select(Address)\n",
    "            .join(User)\n",
    "            .where(User.name == 'sandy')\n",
    "            .where(Address.email_address == 'sandy@sqlalchemy.org')\n",
    "        )\n",
    "\n",
    "        sandy_address = session.execute(stmt).scalars().one()\n",
    "        print(sandy_address)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 修改对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:35:19,181 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:35:19,183 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname \n",
      "FROM user_account \n",
      "WHERE user_account.name = ?\n",
      "2024-07-28 20:35:19,184 INFO sqlalchemy.engine.Engine [generated in 0.00094s] ('patrick',)\n",
      "2024-07-28 20:35:19,187 INFO sqlalchemy.engine.Engine SELECT address.id AS address_id, address.user_id AS address_user_id, address.email_address AS address_email_address \n",
      "FROM address \n",
      "WHERE ? = address.user_id\n",
      "2024-07-28 20:35:19,188 INFO sqlalchemy.engine.Engine [generated in 0.00068s] (3,)\n",
      "2024-07-28 20:35:19,189 INFO sqlalchemy.engine.Engine INSERT INTO address (user_id, email_address) VALUES (?, ?)\n",
      "2024-07-28 20:35:19,189 INFO sqlalchemy.engine.Engine [generated in 0.00050s] (3, 'patrickstar@sqlalchemy.org')\n",
      "2024-07-28 20:35:19,190 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        stmt = (\n",
    "            select(User)\n",
    "            .where(User.name == 'patrick')\n",
    "        )\n",
    "        patrick = session.execute(stmt).scalars().one()\n",
    "        patrick.addresses.append(\n",
    "            Address(email_address='patrickstar@sqlalchemy.org')\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:41:31,128 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:41:31,129 INFO sqlalchemy.engine.Engine SELECT address.id, address.user_id, address.email_address \n",
      "FROM address JOIN user_account ON user_account.id = address.user_id \n",
      "WHERE user_account.name = ? AND address.email_address = ?\n",
      "2024-07-28 20:41:31,130 INFO sqlalchemy.engine.Engine [cached since 673.1s ago] ('sandy', 'sandy@sqlalchemy.org')\n",
      "2024-07-28 20:41:31,131 INFO sqlalchemy.engine.Engine UPDATE address SET email_address=? WHERE address.id = ?\n",
      "2024-07-28 20:41:31,131 INFO sqlalchemy.engine.Engine [generated in 0.00036s] ('sandy_cheeks@sqlalchemy.org', 2)\n",
      "2024-07-28 20:41:31,132 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        stmt = (\n",
    "            select(Address)\n",
    "            .join(User)\n",
    "            .where(User.name == 'sandy')\n",
    "            .where(Address.email_address == 'sandy@sqlalchemy.org')\n",
    "        )\n",
    "        sandy_address = session.execute(stmt).scalar()\n",
    "        sandy_address.email_address = 'sandy_cheeks@sqlalchemy.org'\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 删除对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:47:02,200 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:47:02,202 INFO sqlalchemy.engine.Engine SELECT user_account.id AS user_account_id, user_account.name AS user_account_name, user_account.fullname AS user_account_fullname \n",
      "FROM user_account \n",
      "WHERE user_account.id = ?\n",
      "2024-07-28 20:47:02,203 INFO sqlalchemy.engine.Engine [generated in 0.00064s] (2,)\n",
      "2024-07-28 20:47:02,204 INFO sqlalchemy.engine.Engine SELECT address.id AS address_id, address.user_id AS address_user_id, address.email_address AS address_email_address \n",
      "FROM address \n",
      "WHERE address.id = ?\n",
      "2024-07-28 20:47:02,206 INFO sqlalchemy.engine.Engine [generated in 0.00143s] (2,)\n",
      "2024-07-28 20:47:02,208 INFO sqlalchemy.engine.Engine SELECT address.id AS address_id, address.user_id AS address_user_id, address.email_address AS address_email_address \n",
      "FROM address \n",
      "WHERE ? = address.user_id\n",
      "2024-07-28 20:47:02,209 INFO sqlalchemy.engine.Engine [cached since 703s ago] (2,)\n",
      "2024-07-28 20:47:02,210 INFO sqlalchemy.engine.Engine DELETE FROM address WHERE address.id = ?\n",
      "2024-07-28 20:47:02,211 INFO sqlalchemy.engine.Engine [generated in 0.00048s] (2,)\n",
      "2024-07-28 20:47:02,212 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        sandy = session.get(User, 2)\n",
    "        sandy_address = session.get(Address, 2)\n",
    "        sandy.addresses.remove(sandy_address)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-07-28 20:49:30,298 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n",
      "2024-07-28 20:49:30,300 INFO sqlalchemy.engine.Engine SELECT user_account.id, user_account.name, user_account.fullname \n",
      "FROM user_account \n",
      "WHERE user_account.name = ?\n",
      "2024-07-28 20:49:30,301 INFO sqlalchemy.engine.Engine [cached since 851.1s ago] ('patrick',)\n",
      "2024-07-28 20:49:30,302 INFO sqlalchemy.engine.Engine SELECT address.id AS address_id, address.user_id AS address_user_id, address.email_address AS address_email_address \n",
      "FROM address \n",
      "WHERE ? = address.user_id\n",
      "2024-07-28 20:49:30,302 INFO sqlalchemy.engine.Engine [cached since 851.1s ago] (3,)\n",
      "2024-07-28 20:49:30,304 INFO sqlalchemy.engine.Engine DELETE FROM address WHERE address.id = ?\n",
      "2024-07-28 20:49:30,304 INFO sqlalchemy.engine.Engine [cached since 148.1s ago] (4,)\n",
      "2024-07-28 20:49:30,305 INFO sqlalchemy.engine.Engine DELETE FROM user_account WHERE user_account.id = ?\n",
      "2024-07-28 20:49:30,305 INFO sqlalchemy.engine.Engine [generated in 0.00031s] (3,)\n",
      "2024-07-28 20:49:30,305 INFO sqlalchemy.engine.Engine COMMIT\n"
     ]
    }
   ],
   "source": [
    "with Session(engine) as session:\n",
    "    with session.begin():\n",
    "        result = session.execute(\n",
    "            select(User)\n",
    "            .where(User.name == 'patrick')\n",
    "        )\n",
    "        patrick = result.scalar()\n",
    "        session.delete(patrick)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 映射类配置"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 关系配置"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 查询指引"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用会话"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 事件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 异常"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 示例"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
